#include <Globals.h>
#include <Kernel.h>
#include <DescriptorTables.h>
#include <GenericFaultHandler.h>
#include <PageFaultHandler.h>
#include <DebugExceptionHandler.h>
#include <MonitorDriver.h>
#include <Multiboot.h>
#include <MemoryManagement.h>
#include <Timer.h>
#include <Asm.h>
#include <ProcessManager.h>
#include <SerialDriver.h>
#include <SyscallDispatcher.h>
#include <RemoteResource.h>
#include <Resource.h>
#include <InitrdFilesystem.h>
#include <CharDevice.h>
#include <BlockDevice.h>
#include <IRQDispatcher.h>

Kernel::Kernel()
{
}


Kernel::~Kernel()
{
}

void Kernel::run()
{

  // Make sure we aren't overwriting anything by writing at placementAddress.
  relocatePlacementAddress();

  monitor.init();
  serial.init();

  // Tell our kernel elf parser about our symbol table
  kernelElfParser.loadKernel( multiboot.getSymtabStart(), 
                              multiboot.getStrtabStart() );

  descriptorTables.registerIsr(14, &pageFaultHandler);
  descriptorTables.registerIsr(1, &debugHandler);

  descriptorTables.initGdt();
  descriptorTables.initIdt();
  descriptorTables.initIrq();
  processManager = new ProcessManager();

  memoryManager.initialise( multiboot.getUpperMem()*1024 );
  timer = new Timer();

  descriptorTables.registerIrq(0, timer);
  timer->init(100);

  PageDirectory *newd = new PageDirectory();

  PageDirectory *newDirectory = memoryManager.getKernelDirectory()->clone();
  memoryManager.setCurrentDirectory(newDirectory);
  writePageDirectory((Address)newDirectory->getPhysical());
  memoryManager.remapStack();

  processManager->init();

  descriptorTables.initSyscalls();
  descriptorTables.registerIsr(30, (InterruptServiceRoutine *)&syscallDispatcher);
  descriptorTables.registerIsr(50, (InterruptServiceRoutine *)&syscallDispatcher);
  descriptorTables.registerIsr(51, (InterruptServiceRoutine *)&syscallDispatcher);

  irqDispatcher = new IRQDispatcher();

  if (multiboot.getModStart() == 0)
  {
    PANIC("Initial ramdisk not loaded!");
  }
  
  initrd = new InitrdFilesystem(multiboot.getModStart());
  filesystem = initrd->getFilesystem();
  
  startInterrupts();
  
  processManager->switchToUserMode();

  int ret = syscall(SYSCALL_FORK);

  if (ret == 0)
  {
    char *argv[] = {NULL}; char *env[] = {NULL};
//     asm volatile("int $30"::"a"(SYSCALL_EXECVE),"b"((int)"/init"),"c"((int)argv),"d"((int)env));
    u32int res = requestResource("ProcessManager");
    u32int p = callResource(res, ProcessManager_IF::getProcess, -1);
    callResource(p, Process_IF::execve, "/init", argv, env);
    processManager->getProcess()->execve((char*)"/init",Process::StringList(),
                             Process::StringList());
//     kerr << "Execve failed.\n";
  }

  for(;;)
  {
#ifdef AUTO_RESTART
    if (timer->getTick()/1000 > AUTO_RESTART)
    {
      // automatically restart.
    }
#endif
    syscall(SYSCALL_YIELD);
  }
  
}

void Kernel::relocatePlacementAddress()
{
  Address newPlacementAddress = memoryManager.getPlacementAddress();
  if (multiboot.isElf() && 
      multiboot.getSymtabEnd() > newPlacementAddress)
  {
    newPlacementAddress = multiboot.getSymtabEnd();
  }
  if (multiboot.isElf() &&
      multiboot.getStrtabEnd() > newPlacementAddress)
  {
    newPlacementAddress = multiboot.getStrtabEnd();
  }
  if (multiboot.getModStart() > newPlacementAddress)
  {
    newPlacementAddress = multiboot.getModEnd();
  }
  
  memoryManager.setPlacementAddress(newPlacementAddress);
}

Address Kernel::backtrace(Address basePointer, Address &returnAddress)
{
  // We take a stack base pointer (in basePointer), return what it's pointing at
  // and put the Address just above it in the stack in returnAddress.
  Address nextBase = * ((Address*) basePointer);
  returnAddress    = * ((Address*) (basePointer+sizeof(Address)));
  return nextBase;
}

Address Kernel::backtrace(int n)
{
  Address basePointer = readBasePointer();
  Address ebp = basePointer;
  Address eip = 1;
  int i = 0;
  while (ebp && eip /*&& eip < 0x87000000*/)
  {
    ebp = backtrace(ebp, eip);
    if (i == n)
    {
      return eip;
    }
    i++;
  }
  return 0;
}

void Kernel::printBacktrace(Address basePointer, int n)
{
  Address eip = 1; // Don't initialise to 0, will kill the loop immediately.
  if (basePointer == NULL)
  {
    basePointer = readBasePointer();
  }
  Address ebp = basePointer;
  kerr.write("[BTRACE\t] ", green);
  kerr << "Tracing " << dec << n << "stack frames:" << endl;
  int i = 0;
  while (ebp && eip &&
         ( (n && i<n) || !n) &&
         eip < 0x87000000)
  {
    Address ebp1 = ebp;
    ebp = backtrace(ebp, eip);
    u32int offset;
    char *symbol = kernelElfParser.findSymbol(eip, &offset);
    offset = eip - offset;
    kerr.write("[BTRACE\t] ", green);
    kerr << "  | At " << hex << eip << " <" << symbol << "+" <<
         offset << ">" << endl;

    i++;
  }
}

void Kernel::printStacktrace(u32int n)
{
  Address esp = readStackPointer();
  for (int i = 0; i < n; i++)
  {
    kerr.writeHex(*(Address*)esp);
    kerr.put(' ');
    esp += sizeof(Address);
  }
  serial.put('\n');
}

